using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Schema;
using System.Xml.XPath;
using CriticalSystems.Instrumentation.Internal;

namespace CriticalSystems.Instrumentation
{
    internal sealed class SystemDeploymentParameters
    {
        private const string SubsystemsParameterName = "subsystems";
        private const string SystemParameterName = "system";
        private readonly Assembly callingAssembly;
        private readonly Regex subsystemsPattern =
            new Regex(@"(?<subsystemName>\w{1,25})(:(?<subsystemInstance>\w{1,25}))?", RegexOptions.ExplicitCapture);
        private readonly Regex systemPattern = new Regex(@"(?<systemInstance>\w{1,25})", RegexOptions.ExplicitCapture);

        public SystemDeploymentParameters(Assembly callingAssembly)
        {
            this.callingAssembly = callingAssembly;
            FindSystemInformationResource();
        }

        public SystemInformation SystemInformation { get; private set; }

        private void FindSystemInformationResource()
        {
            var xmlStreams = ResourceUtilities.GetResources(callingAssembly, @".+\.xml");
            foreach (var xmlStream in xmlStreams)
            {
                try
                {
                    IXPathNavigable doc =
                        new XPathDocument(
                            SchemaValidationHelper.GetValidatingReader(SchemaValidationHelper.SystemInformationSchema,
                                                                       xmlStream));
                    if (SystemInformation != null)
                    {
                        throw new ConfigurationErrorsException(
                            "There is more than one system information resource defined in this installer");
                    }
                    SystemInformation = new SystemInformation(doc);
                }
                    // ReSharper disable EmptyGeneralCatchClause
                catch (XmlSchemaValidationException)
                    // ReSharper restore EmptyGeneralCatchClause
                {
                }
            }
            if (SystemInformation == null)
            {
                throw new ConfigurationErrorsException(
                    "There is no system information resource defined in this installer");
            }
        }

        public void ParseParameters(StringDictionary commandLineParameters)
        {
            var systemParameter = commandLineParameters[SystemParameterName];
            var subsystemsParameter = commandLineParameters[SubsystemsParameterName];
            ParseParameters(systemParameter, subsystemsParameter);
        }

        public void ParseParameters(IDictionary stateParameters)
        {
            var systemParameter = (string)stateParameters[SystemParameterName];
            var subsystemsParameter = (string)stateParameters[SubsystemsParameterName];
            ParseParameters(systemParameter, subsystemsParameter);
        }

        private void ParseParameters(string systemParameter, string subsystemsParameter)
        {
            string systemInstance = null;
            if (!string.IsNullOrEmpty(systemParameter))
            {
                var match = systemPattern.Match(systemParameter);
                if (!match.Success)
                {
                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                              "Invalid system instance parameter: {0}", systemParameter));
                }
                systemInstance = match.Groups["systemInstance"].Value;
            }
            SystemInformation.Instance = systemInstance;
            if (string.IsNullOrEmpty(subsystemsParameter))
            {
                // Assume all subsystems being installed so set all subsystems to have default instance.
                foreach (var subsystem in SystemInformation.Subsystems)
                {
                    subsystem.AddInstance(string.Empty);
                }
            }
            else
            {
                var matches = subsystemsPattern.Matches(subsystemsParameter);
                if (matches.Count == 0)
                {
                    throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                              "Invalid subsystems parameter: {0}", subsystemsParameter));
                }
                foreach (Match match in matches)
                {
                    if (match.Success)
                    {
                        var subsystemInstance = match.Groups["subsystemInstance"].Value;
                        var subsystemName = match.Groups["subsystemName"].Value;
                        var subsystem = SystemInformation[subsystemName];
                        if (subsystem == null)
                        {
                            throw new ArgumentException(string.Format(CultureInfo.InvariantCulture,
                                                                      "{0} is not a defined subsystem",
                                                                      subsystemName));
                        }
                        subsystem.AddInstance(subsystemInstance);    
                    }
                }
            }
            IList<SubsystemInformation> unusedSubsystems = new List<SubsystemInformation>();
            foreach (var subsystem in SystemInformation.Subsystems)
            {
                if (subsystem.Instances.Count == 0)
                {
                    unusedSubsystems.Add(subsystem);
                }
            }
            foreach (var subsystem in unusedSubsystems)
            {
                SystemInformation.Subsystems.Remove(subsystem);
            }
        }

        public static void SaveState(IDictionary stateParameters, StringDictionary commandLineParameters)
        {
            var systemParameter = commandLineParameters[SystemParameterName];
            var subsystemsParameter = commandLineParameters[SubsystemsParameterName];
            stateParameters[SystemParameterName] = systemParameter;
            stateParameters[SubsystemsParameterName] = subsystemsParameter;
        }
    }
}